package com.yunji.aspect;

import com.yunji.common.base.response.ResponseHelper;
import com.yunji.common.constant.Constants;
import com.yunji.common.event.EventConstant;
import com.yunji.common.event.RedissonEventBus;
import com.yunji.common.event.entity.RedissonEvent;
import com.yunji.common.model.domain.SysMenuDO;
import com.yunji.common.model.dto.CbWebLogDTO;
import com.yunji.common.model.dto.LoginInfo;
import com.yunji.common.utils.JsonUtil;
import com.yunji.common.utils.StringUtils;
import com.yunji.common.utils.TimeUtils;
import com.yunji.common.utils.bean.BeanUtils;
import com.yunji.common.utils.ip.IpUtils;
import com.yunji.common.utils.uuid.UUID;
import com.yunji.system.context.SystemContext;
import com.yunji.system.context.SystemUtil;
import com.yunji.system.dao.ISysMenuDao;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author : peter-zhu
 * @date : 2024/10/21 16:28
 * @description : TODO
 **/
@Aspect
@Order(1)
@Component
public class RequestLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);

    private static final ThreadLocal<CbWebLogDTO> logDataThreadLocal = ThreadLocal.withInitial(CbWebLogDTO::new);

    @Resource
    private ISysMenuDao sysMenuDao;

    @Pointcut(value = "execution(public * com.yunji.app..*.*(..))")
    public void appController() {
    }

    @Pointcut(value = "execution(public * com.yunji.web..*.*(..))")
    public void webController() {
    }

    @Pointcut(value = "appController() || webController()")
    public void webLog() {
    }

    private static String getClassRequestMapping(Method method) {
        Class<?> targetClass = method.getDeclaringClass();
        RequestMapping classRequestMapping = targetClass.getAnnotation(RequestMapping.class);
        if (classRequestMapping != null && classRequestMapping.value().length > 0) {
            return classRequestMapping.value()[0];
        }
        return null;
    }

    private static String getMethodRequestMapping(Method method) {
//        String mappingName = null;
//        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
//        GetMapping getMapping = method.getAnnotation(GetMapping.class);
//        PostMapping postMapping = method.getAnnotation(PostMapping.class);
//        PutMapping putMapping = method.getAnnotation(PutMapping.class);
//        DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
//        if (requestMapping != null) {
//            System.out.println("Method @RequestMapping value: " + Arrays.toString(requestMapping.value()));
//        } else if (getMapping != null) {
//            System.out.println("Method @GetMapping value: " + Arrays.toString(getMapping.value()));
//        } else if (postMapping != null) {
//            System.out.println("Method @PostMapping value: " + Arrays.toString(postMapping.value()));
//        } else if (putMapping != null) {
//            System.out.println("Method @PostMapping value: " + Arrays.toString(putMapping.value()));
//        } else if (deleteMapping != null) {
//            System.out.println("Method @PostMapping value: " + Arrays.toString(deleteMapping.value()));
//        }
        String[] value = null;
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof RequestMapping) {
                value = ((RequestMapping) annotation).value();
            } else if (annotation instanceof GetMapping) {
                value = ((GetMapping) annotation).value();
            } else if (annotation instanceof PostMapping) {
                value = ((PostMapping) annotation).value();
            } else if (annotation instanceof PutMapping) {
                value = ((PutMapping) annotation).value();
            } else if (annotation instanceof DeleteMapping) {
                value = ((DeleteMapping) annotation).value();
            }
        }
        if (value != null && value.length > 0) {
            return value[0];
        }
        return null;
    }

    private static List<String> extractPathVariables(String path) {
        List<String> variables = new ArrayList<>();
        // 使用正则表达式匹配花括号中的内容
        Pattern pattern = Pattern.compile("\\{([^/]+)\\}");
        Matcher matcher = pattern.matcher(path);
        // 查找所有匹配项
        while (matcher.find()) {
            variables.add(matcher.group(1));  // 获取花括号中的变量
        }
        return variables;
    }

    /**
     * @param : joinPoint
     * @return : void
     * @author : peter-zhu
     * @date : 2024/10/22 9:47
     * @description : 请求前拦截日志
     */
    @Before(value = "webLog()")
    public void doBefore(JoinPoint joinPoint) {
        long startTime = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            if (apiOperation == null) {
                return;
            }
            //logger.debug("ApiOperation : " + apiOperation.value());
            //logger.debug("URL : " + request.getRequestURL().toString());
            //logger.debug("REQUEST_MAPPING : " + request.getRequestURI());
            //logger.debug("HTTP_METHOD : " + request.getMethod());
            //logger.debug("IP : " + request.getRemoteAddr());
            //logger.debug("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            boolean effectiveMapping = false;
            String requestMapping = "";
            String classRequestMapping = getClassRequestMapping(method);
            String methodRequestMapping = getMethodRequestMapping(method);
            if (classRequestMapping != null) {
                requestMapping = requestMapping + classRequestMapping;
            }
            if (methodRequestMapping != null) {
                requestMapping = requestMapping + methodRequestMapping;
            }
            String basePath = null;
            List<String> matchParams = null;
            if (StringUtils.hasText(requestMapping)) {
                basePath = requestMapping;
                effectiveMapping = true;
                int index = requestMapping.indexOf("/{");
                if (index != -1) {
                    basePath = requestMapping.substring(0, index);
                }
                matchParams = extractPathVariables(requestMapping);
            }
            HttpServletRequest request = attributes.getRequest();
//            System.out.println("requestMapping----------------" + requestMapping);
//            System.out.println("requestBasePath----------------" + basePath);
//            System.out.println("getRequestURI----------------" + request.getRequestURI());
//            System.out.println("matchParams----------------" + JsonUtil.toJsonString(matchParams));
            Object arg = new Object();
            if (BeanUtils.isNotNull(joinPoint.getArgs())) {
                boolean isRequestBody = false;
                Map<String, Object> pathVariableMap = new HashMap<>();
                Object[] args = joinPoint.getArgs();
                Parameter[] parameters = method.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    if (parameter.isAnnotationPresent(PathVariable.class)) {
                        //pathVariableMap.put(parameter.getName(), args[i]);
                        if (matchParams != null && matchParams.size() > i) {
                            pathVariableMap.put(matchParams.get(i), args[i]);
                        }
                    } else if (parameter.isAnnotationPresent(RequestBody.class)) {
                        arg = args[i];
                        isRequestBody = true;
                        break;
                    }
                }
                if (!isRequestBody) {
                    arg = pathVariableMap;
                }
//                boolean isRequestBody = false;
//                for (int i = 0; i < parameterAnnotations.length; i++) {
//                    Annotation[] annotations = parameterAnnotations[i];
//                    for (Annotation annotation : annotations) {
//                        if (annotation instanceof PathVariable) {
//                            PathVariable pathVariable = (PathVariable) annotation;
//                            System.out.println("参数 " + args[i] + " 是 @PathVariable");
//                            pathVariableMap.put(pathVariable.name(), pathVariable.value());
//                            System.out.println("参数 " + pathVariable.name() + " value: " + pathVariable.value());
//                        } else if (annotation instanceof RequestBody) {
//                            arg = args[i];
//                            isRequestBody = true;
//                            break;
//                        }
//                    }
//                }
//                if (!isRequestBody) {
//                    arg = pathVariableMap;
//                }
            }
            CbWebLogDTO c = new CbWebLogDTO();
            c.setRequestBasePath(basePath);
            c.setRequestId(UUID.fastUUID().toString());
            c.setUrl(request.getRequestURL().toString());
            c.setIp(IpUtils.getIpAddr());
            c.setHttpMethod(request.getMethod());
            c.setClassMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            c.setRequestMapping(requestMapping);
            c.setRequestMappingUri(request.getRequestURI());
            c.setRequestData(arg);
            c.setOperateTime(TimeUtils.parseDateToStr(TimeUtils.YYYY_MM_DD_HH_MM_SS, new Date()));
            c.setApiOperation(apiOperation.value());
            c.setSystem(Constants.SYSTEM_NAME);
            c.setRequestType(request.getMethod().toUpperCase());
            c.setCostTime(startTime);
            if (effectiveMapping) {
                List<SysMenuDO> sysMenuById = sysMenuDao.getSysMenuById(c.getRequestBasePath(), request.getMethod());
                if (!CollectionUtils.isEmpty(sysMenuById)) {
                    logger.trace("RequestLogAspect weblog sysMenu : {}", JsonUtil.toJsonString(sysMenuById));
                    Set<String> menu = sysMenuById.stream().map(SysMenuDO::getMenuName).collect(Collectors.toSet());
                    c.setMenuNameList(menu);
                    SysMenuDO sysMenu = sysMenuById.stream().findFirst().orElse(null);
                    if (sysMenu != null) {
                        c.setTableName(sysMenu.getTableName());
                        c.setRelativeUri(sysMenu.getRelativeUri());
                        c.setRequestUri(sysMenu.getRequestUri());
                        c.setOperateType(sysMenu.getOperateType());
                    }
                }
            }
            Class<?> targetClass = method.getDeclaringClass();
            Api apiAnnotation = targetClass.getAnnotation(Api.class);
            if (apiAnnotation != null) {
                c.setApi(StringUtils.isEmpty(apiAnnotation.value()) ? Arrays.toString(apiAnnotation.tags()) : apiAnnotation.value());
            }
            logDataThreadLocal.set(c);
            c.setAspectTime(System.currentTimeMillis() - startTime);
        }
    }

    /**
     * 请求后日志处理
     *
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) {
        CbWebLogDTO cbWebLogDTO = logDataThreadLocal.get();
        cbWebLogDTO.setResponseData(ret);
        cbWebLogDTO.setSuccess(true);
        sendWebLog(cbWebLogDTO);
    }

    @AfterThrowing(pointcut = "webLog()", throwing = "e")
    public void doAfterThrowing(Throwable e) {
        CbWebLogDTO cbWebLogDTO = logDataThreadLocal.get();
        cbWebLogDTO.setResponseData(ResponseHelper.failure(-1, e.getMessage()));
        cbWebLogDTO.setSuccess(false);
        boolean b = sendWebLog(cbWebLogDTO);
        if (!b)
            logger.debug("跨域询问请求不触发日志：{}", cbWebLogDTO);
    }

    private static boolean sendWebLog(CbWebLogDTO cbWebLogDTO) {
        long sendStartTime = System.currentTimeMillis();
        if (StringUtils.isEmpty(cbWebLogDTO.getRequestMappingUri()))
            return false;
        LoginInfo currentUser = SystemContext.getUser();
        if (currentUser != null) {
            cbWebLogDTO.setBelongTo(currentUser.getBelongName());
            cbWebLogDTO.setNickname(currentUser.getNickname());
            cbWebLogDTO.setUsername(currentUser.getUsername());
        }
        //平台用户
//        if (currentUser != null) {
//
//        } else {
//            // 微信用户
//            MemberDO wechatUser = SystemContext.getWechatUser();
//            if (wechatUser != null) {
//                cbWebLogDTO.setNickname(wechatUser.getName());
//                cbWebLogDTO.setUsername(wechatUser.getPhone());
//            }
//        }
        long currentTime = System.currentTimeMillis();
        cbWebLogDTO.setCostTime(currentTime - cbWebLogDTO.getCostTime());
        cbWebLogDTO.setAspectTime(cbWebLogDTO.getAspectTime() + (currentTime - sendStartTime));

        String jsonString = JsonUtil.toJsonString(cbWebLogDTO);
        if (StringUtils.isEmpty(jsonString)) {
            //logger.error("RequestLogAspect sendWebLog error logData:{}", cbWebLogDTO);
            return false;
        }
        RedissonEventBus.getInstance().publishAsync(new RedissonEvent<>(EventConstant.WEB_LOG_QUEUE_KEY, jsonString));
        //移除本地线程的数据
        logDataThreadLocal.remove();
        logger.trace("webLog()切面日志数据 : {}", cbWebLogDTO);
        return true;
    }
}
